﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Services.MSBuild.Tasks.Core;
using Microsoft.Build.Framework;
using System.Collections;
using System.IO;
using System.Text.RegularExpressions;

namespace Microsoft.Services.MSBuild.Tasks
{
    public sealed class SetVersionNumber
        : TaskBase
    {
        private static readonly Regex VersionPattern = new Regex(@"Assembly(?<type>(Version|FileVersion|InformationalVersion))(?:Attribute)?\(""(?<major>\d{1,3})\.(?<minor>\d{1,3})\.(?<build>\d{1,3})\.(?<revision>\d{1,3})""\)", RegexOptions.Compiled | RegexOptions.Multiline);

        private int? _major = null;
        private int? _minor = null;
        private int? _build = null;
        private int? _revision = null;

        [Required]
        public ITaskItem[] Files
        { get; set; }

        [Output]
        public ITaskItem[] PatchedFiles
        { get; set; }

        public int Major
        {
            get { return _major.GetValueOrDefault(); }
            set { _major = (value < 0 ? ((int?)null) : value); } 
        }

        public int Minor
        {
            get { return _minor.GetValueOrDefault(); }
            set { _minor = (value < 0 ? ((int?)null) : value); }
        }

        public int Build
        {
            get { return _build.GetValueOrDefault(); }
            set { _build = (value < 0 ? ((int?)null) : value); }
        }

        public int Revision
        {
            get { return _revision.GetValueOrDefault(); }
            set { _revision = (value < 0 ? ((int?)null) : value); }
        }

        public override bool Execute()
        {
            ArrayList list = new ArrayList();

            foreach (ITaskItem item in this.Files)
            {
                try
                {
                    bool patched = PatchFile(item.ItemSpec);

                    if (patched) list.Add(item);
                }
                catch (Exception exc)
                {
                    this.Log.LogError("Failed to patch '{0}'. Error: {1}", item.ItemSpec, exc);
                }
            }

            this.PatchedFiles = (ITaskItem[])list.ToArray(typeof(ITaskItem));

            return !base.Log.HasLoggedErrors;
        }

        private bool PatchFile(string path)
        {
            bool dirty = false;

            FileInfo file = new FileInfo(path);

            if (file.Exists)
            {
                string content;

                using (StreamReader sr = file.OpenText())
                {
                     content = sr.ReadToEnd();
                }

                if (!string.IsNullOrEmpty(content))
                {
                    string patched = VersionPattern.Replace(content, match =>
                        {
                            int curMajor = int.Parse(match.Groups["major"].Value);
                            int curMinor = int.Parse(match.Groups["minor"].Value);
                            int curBuild = int.Parse(match.Groups["build"].Value);
                            int curRevision = int.Parse(match.Groups["revision"].Value);

                            Version version = new Version(
                                _major.GetValueOrDefault(curMajor),
                                _minor.GetValueOrDefault(curMinor),
                                _build.GetValueOrDefault(curBuild),
                                _revision.GetValueOrDefault(curRevision));

                            string value = string.Format("Assembly{0}(\"{1}\")", match.Groups["type"].Value, version);

                            if (match.Value != value)
                            {
                                dirty = true;

                                this.Log.LogMessage("Patching: {0} -> {1} ({2})", match.Value, value, path);
                            }

                            return value;
                        });

                    if (dirty)
                    {
                        File.SetAttributes(path, File.GetAttributes(path) & ~FileAttributes.ReadOnly);

                        File.WriteAllText(path, patched);
                    }
                }
            }

            return dirty;
        }
    }
}
